package vfs.service.impl;

import vfs.persist.entity.build.impl.VFSUserBuilder;
import java.sql.Connection;
import vfs.obj.entity.VFSUser;
import javax.ws.rs.QueryParam;
import javax.ws.rs.Produces;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import java.util.HashMap;
import java.util.Map;
import vfs.filter.VFSFilter.VFSFilterOperator;
import vfs.filter.impl.VFSFilterImpl;
import vfs.filter.VFSFilter;
import vfs.service.VFSNodeService;
import vfs.obj.association.VFSNodeToParent;
import vfs.persist.entity.build.impl.VFSNodeTypeBuilder;
import vfs.obj.entity.VFSNodeType;
import vfs.obj.association.VFSNodeToNodeType;
import java.util.List;
import java.util.ArrayList;
import vfs.obj.entity.VFSPathDescendant;
import vfs.obj.entity.VFSPathAncestor;
import java.util.Iterator;
import org.json.simple.JSONValue;
import vfs.obj.association.VFSPath;
import vfs.obj.association.impl.VFSAssociationFactoryImpl;
import vfs.obj.association.VFSAssociationFactory;
import vfs.persist.entity.build.impl.VFSNodeBuilder;
import vfs.persist.entity.manage.impl.VFSEntityManagerImpl;
import vfs.persist.entity.manage.VFSEntityManager;
import vfs.obj.entity.VFSNode;
import vfs.except.VFSException;
import vfs.obj.entity.VFSEntityFactory;
import vfs.obj.entity.impl.VFSEntityFactoryImpl;
import vfs.util.VFSPersistUtil;

import static vfs.util.VFSUtil.getPublicAccess;

/**
 *
 * @author Matthew Eavenson <durandal@uga.edu>
 * 
 */
@Path("/node")
public class VFSNodeServiceImpl implements VFSNodeService{
    
    private static VFSNode root;
    
    static{
        try{
            Connection conn = VFSPersistUtil.connect();
            VFSEntityManager<VFSNode> manager = new VFSEntityManagerImpl<VFSNode>(conn, new VFSNodeBuilder());
            root = manager.get(1);
            conn.close();
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }
    
    @GET @Path("/info")
    @Produces("application/json")
    @Override
    public String info( @QueryParam("uid")  String uid, 
                        @QueryParam("dir")  long workingDir, 
                        @QueryParam("path") String pathStr ){
        
        Connection conn = VFSPersistUtil.connect();
        Map<String,Object> response = new HashMap<String,Object>();
        
        VFSUser user = getUser(conn, uid);
        if(user == null){
            response.put("error", "Invalid user id");
        }else{
            VFSEntityManager<VFSNode> manager = new VFSEntityManagerImpl<VFSNode>(conn, new VFSNodeBuilder());
            VFSAssociationFactory aFactory = new VFSAssociationFactoryImpl(conn);
            try{
                VFSNode node = manager.get(workingDir);
                VFSPath path = aFactory.getPath();
                if(pathStr != null)
                    node = getDir(path, node, pathStr);
                if(node != null){
                    Map<String,Object> info = getNodeInfo(node, aFactory, true);
                    String nodePath = getPath(node, path);
                    info.put("path", nodePath);
                    response.put("result", info);
                }else{
                    response.put("error", "No such file or directory");
                }
            }catch(VFSException ex){
                response.put("error", ex.getMessage());
            }
        }
        close(conn);
        return JSONValue.toJSONString(response);
    }
    
    @GET @Path("/create")
    @Produces("application/json")
    @Override
    public String create( @QueryParam("uid")      String uid, 
                          @QueryParam("parent")   long parentId, 
                          @QueryParam("name")     String name, 
                          @QueryParam("access")   String access, 
                          @QueryParam("loc")      String location, 
                          @QueryParam("type")     Long nodeType ){
        
        Connection conn = VFSPersistUtil.connect();
        Map<String,Object> response = new HashMap<String,Object>();
        
        VFSUser user = getUser(conn, uid);
        if(user == null){
            response.put("error", "Invalid user id");
        }else{
            VFSEntityFactory eFactory = new VFSEntityFactoryImpl(conn);
            VFSAssociationFactory aFactory = new VFSAssociationFactoryImpl(conn);
            VFSEntityManager<VFSNode> nManager = new VFSEntityManagerImpl<VFSNode>(conn, new VFSNodeBuilder());

            try{
                VFSNode parent = nManager.get(parentId);

                if(parent != null){
                    VFSNode node = null;
                    if(name.contains("/")){
                        VFSPath path = aFactory.getPath();
                        node = createRecursive(user, eFactory, path, parent, name);
                    }else{
                        node = eFactory.createNode(name, location, getPublicAccess(access), user, parent);
                    }

                    // if node type is given, node is not supposed to be a folder, so set its type
                    if(nodeType != null){
                        VFSNodeToNodeType nft = aFactory.getNodeToNodeType();
                        VFSEntityManager<VFSNodeType> ftm = new VFSEntityManagerImpl<VFSNodeType>(conn, new VFSNodeTypeBuilder());
                        nft.setNodeType(node, ftm.get(nodeType));
                    }

                    response.put("result", getNodeInfo(node, aFactory, true));
                }else response.put("error", "No such file or directory");
            }catch(VFSException ex){
                response.put("error", ex.getMessage());
            }
        }
        close(conn);
        return JSONValue.toJSONString(response);
    }
    
    @Override
    public String remove( String sessionId, long nodeId, String path ){
        return remove(sessionId, nodeId, path, false);
    }
    
    @GET @Path("/remove")
    @Produces("application/json")
    @Override
    public String remove( @QueryParam("uid")     String uid, 
                          @QueryParam("node")    long nodeId, 
                          @QueryParam("path")    String pathStr,
                          @QueryParam("recurse") Boolean recursive ){
        
        Connection conn = VFSPersistUtil.connect();
        Map<String,String> response = new HashMap<String,String>();
        VFSUser user = getUser(conn, uid);
        if(user == null){
            response.put("error", "Invalid user id");
        }else{
            VFSEntityFactory eFactory = new VFSEntityFactoryImpl(conn);
            VFSAssociationFactory aFactory = new VFSAssociationFactoryImpl(conn);
            VFSEntityManager<VFSNode> manager = new VFSEntityManagerImpl<VFSNode>(conn, new VFSNodeBuilder());
            try{
                VFSNode node = manager.get(nodeId);
                VFSPath path = aFactory.getPath();

                if(pathStr != null) node = getDir(path, node, pathStr);

                if(node != null){
                    VFSNodeToNodeType nft = aFactory.getNodeToNodeType();
                    if(nft.getNodeType(node) == null && !recursive){
                        response.put("error", node.getName()+": is a directory");
                    }else eFactory.removeNode(node);
                }
                response.put("result", "Remove successful");
            }catch(VFSException ex){
                response.put("error", ex.getMessage());
            }
        }
        close(conn);
        return JSONValue.toJSONString(response);
    }
    
    @Override
    public String list( String sessionId, long nodeId, String subPath ){
        return list(sessionId, nodeId, subPath, false);
    }
    
    @GET @Path("/list")
    @Produces("application/json")
    @Override
    public String list( @QueryParam("uid")    String uid, 
                        @QueryParam("node")    long nodeId, 
                        @QueryParam("path")    String pathStr, 
                        @QueryParam("verbose") Boolean verbose ){
        
        Connection conn = VFSPersistUtil.connect();
        Map<String,Object> response = new HashMap<String,Object>();
        VFSUser user = getUser(conn, uid);
        if(user == null){
            response.put("error", "Invalid user id");
        }else{
            VFSEntityManager<VFSNode> manager = new VFSEntityManagerImpl<VFSNode>(conn, new VFSNodeBuilder());
            VFSAssociationFactory aFactory = new VFSAssociationFactoryImpl(conn);
            try{
                VFSNode node = manager.get(nodeId);
                VFSPath path = aFactory.getPath();

                if(pathStr != null) node = getDir(path, node, pathStr);

                if(node != null){
                    List<Map<String,Object>> nodeList = new ArrayList<Map<String,Object>>();
                    for(Iterator<VFSNode> children = path.getDescendants(node, 1); children.hasNext();){
                        VFSNode child = children.next();
                        nodeList.add(getNodeInfo(child, aFactory, verbose));
                    }
                    response.put("result", nodeList);
                }else response.put("error", "No such file or directory");
            }catch(VFSException ex){
                response.put("error", ex.getMessage());
            }   
        }
        close(conn);
        return JSONValue.toJSONString(response);
    }
    
    @GET @Path("/rename")
    @Produces("application/json")
    @Override
    public String rename( @QueryParam("uid")  String uid, 
                          @QueryParam("node") long nodeId, 
                          @QueryParam("name") String name ){
        
        Connection conn = VFSPersistUtil.connect();
        Map<String,String> response = new HashMap<String,String>();
        VFSUser user = getUser(conn, uid);
        if(user == null){
            response.put("error", "Invalid user id");
        }else{
            VFSEntityManager<VFSNode> manager = new VFSEntityManagerImpl<VFSNode>(conn, new VFSNodeBuilder());
            try{
                VFSNode node = manager.get(nodeId);
                node.setName(name);
                manager.update(node);
                response.put("result", "Rename successful");
            }catch(VFSException ex){
                response.put("error", ex.getMessage());
            }
        }
        close(conn);
        return JSONValue.toJSONString(response);
    }
    
    @GET @Path("/move")
    @Produces("application/json")
    @Override
    public String move( @QueryParam("uid")  String uid, 
                        @QueryParam("dir")  long nodeId, 
                        @QueryParam("path") String srcPathStr, 
                        @QueryParam("dest") String destPathStr ){
        
        Connection conn = VFSPersistUtil.connect();
        Map<String,String> response = new HashMap<String,String>();
        VFSUser user = getUser(conn, uid);
        if(user == null){
            response.put("error", "Invalid user id");
        }else{
            try{
                VFSEntityManager<VFSNode> manager = new VFSEntityManagerImpl<VFSNode>(conn, new VFSNodeBuilder());
                VFSNode location = manager.get(nodeId);
                VFSAssociationFactory aFactory = new VFSAssociationFactoryImpl(conn);
                VFSPath path = aFactory.getPath();

                VFSNode node = getDir(path, location, srcPathStr);
                VFSNode destination = getDir(path, location, destPathStr);

                if(node == null){
                    response.put("error", srcPathStr+": No such file or directory");
                }else if(destination == null){
                    response.put("error", destPathStr+": No such file or directory");
                }else return move(uid, node.getId(), destination.getId());
            }catch(VFSException ex){
                response.put("error", ex.getMessage());
            }
        }
        close(conn);
        return JSONValue.toJSONString(response);
    }
    
    @Override
    public String move( @QueryParam("uid")  String uid, 
                        @QueryParam("node") long nodeId, 
                        @QueryParam("dest") long destId ){
        
        Connection conn = VFSPersistUtil.connect();
        Map<String,String> response = new HashMap<String,String>();
        
        VFSUser user = getUser(conn, uid);
        if(user == null){
            response.put("error", "Invalid user id");
        }else{
            VFSAssociationFactory aFactory = new VFSAssociationFactoryImpl(conn);
            VFSEntityManager<VFSNode> manager = new VFSEntityManagerImpl<VFSNode>(conn, new VFSNodeBuilder());
            VFSNodeToParent ntp = aFactory.getNodeToParent();
            try{
                // get node and destination
                VFSNode node = manager.get(nodeId);
                VFSNode destination = manager.get(destId);

                // if both nodes exist
                if(node != null && destination != null){

                    // check to make sure the node we're moving to is actually a folder
                    VFSNodeToNodeType nft = aFactory.getNodeToNodeType();
                    if(nft.getNodeType(destination) != null){
                        response.put("error", "Destination is not a folder");
                    }else{
                        VFSPath path = aFactory.getPath();

                        // cache the list of ancestors, since we're potentially going to be using it a lot
                        List<VFSPathAncestor> ancestorList = new ArrayList<VFSPathAncestor>();
                        for(Iterator<VFSPathAncestor> ancestors = path.getAncestors(destination, null); ancestors.hasNext();){
                            ancestorList.add(ancestors.next());
                        }

                        // iterate through the node's descendants, removing all ancestors above the
                        // node's level, and adding the list of new ancestors
                        for(Iterator<VFSPathDescendant> descendants = path.getDescendants(node, null); descendants.hasNext();){
                            VFSPathDescendant descendant = descendants.next();
                            VFSNode descNode = descendant.getDescendant();
                            int length = descendant.length();
                            path.removeAncestors(descNode, length+1);

                            // add new ancestors above the new parent
                            for(VFSPathAncestor ancestor: ancestorList){
                                path.addPath(ancestor.getAncestor(), descNode, ancestor.length()+length+1);
                            }
                            // add the new parent as an ancestor
                            path.addPath(destination, descNode, length+1);
                        }

                        // remove all the old ancestors from this node and add the new ancestors
                        path.removeAncestors(node, 1);
                        for(VFSPathAncestor ancestor: ancestorList){
                            path.addPath(ancestor.getAncestor(), node, ancestor.length()+1);
                        }
                        path.addPath(destination, node, 1);

                        // update the parent foreign key
                        ntp.setParent(node, destination);

                        response.put("result", "Move successful");
                    }
                }
            }catch(VFSException ex){
                response.put("error", ex.getMessage());
            }
        }
        close(conn);
        return JSONValue.toJSONString(response);
    }
    
    private static VFSNode getDir(VFSPath path, VFSNode workingDir, String directory){
        VFSNode[] nodeArr = getPath(path, workingDir, directory);
        return nodeArr[nodeArr.length-1];
    }
    
    private static VFSNode[] getPath(VFSPath path, VFSNode workingDir, String directory){
        String[] pathArr = directory.split("/");
        workingDir = directory.startsWith("/")? root: workingDir;
        VFSNode[] nodeArr = new VFSNode[pathArr.length];
        try{
            for(int i=0; i<pathArr.length; i++){
                String folder = pathArr[i];
                if(workingDir == null) break;
                if(folder.equals("..")){
                    workingDir = path.getAncestor(workingDir, 1);
                }else if(folder.equals(".")) {
                }else{
                    VFSFilter filter = new VFSFilterImpl();
                    filter.addConstraint("nodename", folder, VFSFilterOperator.EQ);
                    filter.addConstraint("length", "1", VFSFilterOperator.EQ);
                    Iterator<VFSPathDescendant> children = path.getDescendants(workingDir, filter);
                    if(children.hasNext())
                        workingDir = children.next().getDescendant();
                    else workingDir = null;
                }
                nodeArr[i] = workingDir;
            }
        }catch(VFSException ex){
            ex.printStackTrace();
        }
        return nodeArr;
    }
    
    private static VFSNode createRecursive(VFSUser user, VFSEntityFactory eFactory, VFSPath path, VFSNode workingDir, String directory){
        
        if(directory.startsWith("/")){
            workingDir = root;
            directory = directory.substring(1, directory.length());
        }
        
        String[] pathArr = directory.split("/");
        VFSNode[] nodeArr = getPath(path, workingDir, directory);
        VFSNode parent = workingDir;
        int i=0;
        try{
            for(VFSNode node: nodeArr){
                if(node != null) parent = node;
                else parent = eFactory.createNode(pathArr[i], null, VFSNode.PublicAccess.READ, user, parent);
                i++;
            }
        }catch(VFSException ex){
            ex.printStackTrace();
        }
        
        return parent;
    }
    
    private static String getPath(VFSNode node, VFSPath path){
        
        StringBuilder sb = new StringBuilder();
        try{
            VFSFilter filter = new VFSFilterImpl();
            filter.addConstraint("length", "DESC", VFSFilterOperator.SORTBY);
            for(Iterator<VFSPathAncestor> ancestors = path.getAncestors(node, filter); ancestors.hasNext();){
                VFSNode ancestor = ancestors.next().getAncestor();
                sb.append(ancestor.getName()+"/");
            }
            sb.append(node.getName());
        }catch(VFSException ex){
            ex.printStackTrace();
        }
        return sb.toString();
    }
    
    private Map<String,Object> getNodeInfo(VFSNode node, VFSAssociationFactory aFactory, boolean verbose) throws VFSException{
        Map<String,Object> info = new HashMap<String,Object>();
        info.put("id", node.getId());
        info.put("name", node.getName());
        if(verbose){
            VFSNodeToNodeType nft = aFactory.getNodeToNodeType();
            VFSNodeType type = nft.getNodeType(node);
            if(type != null){
                info.put("typeId", type.getId());
                info.put("typeName", type.getName());
            }
            info.put("createdOn", node.getCreationDate().toString());
            info.put("modifiedOn", node.getModifiedDate().toString());
            info.put("access", node.getPublicAccess().toString());
        }
        return info;
    }
    
    private VFSUser getUser(Connection conn, String uid){
        VFSUser user = null;
        VFSEntityManager<VFSUser> uManager = new VFSEntityManagerImpl<VFSUser>(conn, new VFSUserBuilder());
        VFSFilter uFilter = new VFSFilterImpl();
        uFilter.addConstraint("uid", uid, VFSFilterOperator.EQ);
        
        try{ user = uManager.get(uFilter).next();
        }catch(VFSException ex){}
        return user;
    }
    
    private void close(Connection conn){
        try{ conn.close();
        }catch(Exception ex){ ex.printStackTrace(); }
    }
    
}
